package gorm

import (
	"MoSkeleton/config/consts"
	"MoSkeleton/framework/mo_gorm"
	system2 "MoSkeleton/web/pogos/respose/system"
	"fmt"
	mapset "github.com/deckarep/golang-set/v2"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func DoDataScopeBeforeQuery(db *gorm.DB) {
	if !isDataScopeEnable(db) {
		return
	}
	mo_gorm.IncDataScopeCount(db, 1)
	onlineUserIntf, _ := db.Get(consts.Gorm_Config_OnlineUser)
	onlineUser := onlineUserIntf.(*system2.OnlineUserResp)
	scopeTypeData := &ScopeTypeData{}
	scopeTypeData.FromOnlineUser(onlineUser)
	scopeTypeData.dataScopeQuery(db, onlineUser)
}

type ScopeTypeData struct {
	//ScopeType -> ScopeTypeDataItem
	ScopeDataMap map[string]*ScopeTypeDataItem
}
type ScopeTypeDataItem struct {
	deptId  uint64
	roleIds mapset.Set[uint64]
}

func (s *ScopeTypeData) dataScopeQuery(db *gorm.DB, onlineUser *system2.OnlineUserResp) {
	deptAlias := mo_gorm.GormGet(db, consts.Gorm_Config_Dept_Alias, "")
	userAlias := mo_gorm.GormGet(db, consts.Gorm_Config_User_Alias, "")
	stmt := db.Statement
	exprs := make([]clause.Expression, 0)
	for dataType, scopeDataItem := range s.ScopeDataMap {
		switch dataType {
		case consts.Data_Scope_All:
		//不需要限定
		case consts.Data_Scope_Dept_And_Child:
			s := fmt.Sprintf("%sid in (select id from sys_dept where id = ? or find_in_set(?, ancestors))", deptAlias)
			if conds := stmt.BuildCondition(s, scopeDataItem.deptId, scopeDataItem.deptId); len(conds) > 0 {
				exprs = append(exprs, clause.Or(clause.And(conds...)))
			}
		case consts.Data_Scope_Dept:
			s := fmt.Sprintf("%sid = ?", deptAlias)
			if conds := stmt.BuildCondition(s, scopeDataItem.deptId); len(conds) > 0 {
				exprs = append(exprs, clause.Or(clause.And(conds...)))
			}
		case consts.Data_Scope_Self:
			if userAlias != "" {
				s := fmt.Sprintf("%sid = ?", userAlias)
				if conds := stmt.BuildCondition(s, onlineUser.User.ID); len(conds) > 0 {
					exprs = append(exprs, clause.Or(clause.And(conds...)))
				}
			} else {
				s := fmt.Sprintf("%sid = ?", deptAlias)
				if conds := stmt.BuildCondition(s, -1); len(conds) > 0 {
					exprs = append(exprs, clause.Or(clause.And(conds...)))
				}
			}
		case consts.Data_Scope_Custom:
			var roleIds []uint64
			if !scopeDataItem.roleIds.IsEmpty() {
				roleIds = scopeDataItem.roleIds.ToSlice()
			} else {
				roleIds = []uint64{0}
			}
			s := fmt.Sprintf("%sid IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in (?) ) ", deptAlias)
			if conds := stmt.BuildCondition(s, roleIds); len(conds) > 0 {
				exprs = append(exprs, clause.Or(clause.And(conds...)))
			}
		}
	}
	if len(exprs) > 0 {
		stmt.AddClause(clause.Where{Exprs: []clause.Expression{
			clause.And(exprs...)}})
	}
}

func (s *ScopeTypeData) FromOnlineUser(onlineUser *system2.OnlineUserResp) {
	s.ScopeDataMap = make(map[string]*ScopeTypeDataItem)
	for _, role := range onlineUser.User.Roles {
		if role.DataScope == consts.Data_Scope_All {
			s.ScopeDataMap = make(map[string]*ScopeTypeDataItem)
			scopeItem := &ScopeTypeDataItem{
				deptId:  onlineUser.User.DeptId,
				roleIds: mapset.NewSet[uint64](),
			}
			s.ScopeDataMap[role.DataScope] = scopeItem
			break
		}
		scopeItem, exists := s.ScopeDataMap[role.DataScope]
		if !exists {
			scopeItem = &ScopeTypeDataItem{
				deptId:  onlineUser.User.DeptId,
				roleIds: mapset.NewSet[uint64](),
			}
			scopeItem.roleIds.Add(role.ID)
			s.ScopeDataMap[role.DataScope] = scopeItem
		} else {
			if role.DataScope == consts.Data_Scope_Custom {
				scopeItem.roleIds.Add(role.ID)
			}
		}
	}
	s.tidyScopeDatas()
}

func (s *ScopeTypeData) tidyScopeDatas() {
	if _, exists := s.ScopeDataMap[consts.Data_Scope_Dept_And_Child]; exists {
		delete(s.ScopeDataMap, consts.Data_Scope_Dept)
		delete(s.ScopeDataMap, consts.Data_Scope_Self)
	}
	if _, exists := s.ScopeDataMap[consts.Data_Scope_Dept]; exists {
		delete(s.ScopeDataMap, consts.Data_Scope_Self)
	}
}

func isDataScopeEnable(db *gorm.DB) bool {
	enable, exists := db.Get(consts.Gorm_Config_DataScope)
	ok := exists && consts.U_YES == enable.(string)
	if !ok {
		return false
	}
	count := mo_gorm.GormGet(db, consts.Gorm_Config_DataScope_Count, 0)
	maxCount := mo_gorm.GormGet(db, consts.Gorm_Config_MaxCount_PerReq, consts.Gorm_MaxCount_PerReq_Def)
	if count >= maxCount {
		return false
	}
	return true
}
